package org.jugile.util;


import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.jugile.util.Props;
import org.jugile.util.U;

/**
 * Connection pool
 */
public class DBPool extends Jugile {

	private String name;
	private String URL;
	private String user;	
	private String password;
	private int maxConn;

	private int checkedOut;
	private List<DBConnection> freeConnections = new ArrayList<DBConnection>();

	private final static String CPARAMS = "?autoReconnectForPools=true";
	// TODO: check this for newer mysql!
	//private final static String CPARAMS = "?autoReconnect=true";
	
	static Logger log = Logger.getLogger(DBPool.class);

	protected DBPool() {
		String name = Props.getDbName();
		String host = Props.getDbHost();
		init(name,getUrl(host,name),Props.getDbUser(),Props.getDbPasswd(),30);
	}
	
	protected DBPool(int maxConn) {
		String name = Props.getDbName();
		String host = Props.getDbHost();
		init(name,getUrl(host,name),Props.getDbUser(),Props.getDbPasswd(),maxConn);
	}
	
	protected DBPool(String URL, String name, String user, String password, int maxConn) {
		init(name,URL,user,password,maxConn);
	}

	private String getUrl(String host, String name) {
		String url = Props.get("jugile.db.url");
		String path = Props.get("jugile.db.path");
		String cwd = Props.get("cwd");
		if (!empty(path) && !empty(cwd)) {
			path = cwd + "/" + path;
			print("path: " + path);
			return "jdbc:h2:"+path;
		}
		if (!empty(url)) return url;
		return "jdbc:mysql://"+ host +"/" +name+CPARAMS;
	}
	
	private String getDriverName() {
		String dr = Props.get("jugile.db.driver");
		if (!empty(dr)) return dr;
		return "com.mysql.jdbc.Driver";
	}
	
	private static DBPool pool;
	public static DBPool getPool() {
		if (pool != null) return pool;
		pool = new DBPool();
		return pool;
	}

	private void init(String name, String URL, String user, String password, int maxConn) {
		this.name = name;
		this.URL = URL;
		this.user = user;
		this.password = password;
		this.maxConn = maxConn;		
		try {
			Driver driver = (Driver)Class.forName(getDriverName()).newInstance();
			DriverManager.registerDriver(driver);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	protected synchronized void free(DBConnection conn) {
		// Put the connection at the end of the List
		freeConnections.add(conn);
		checkedOut--;
		notifyAll();
		log.debug("freeConnection(): checkedOut = " + checkedOut +" conn: " + conn);
	}

	public synchronized DBConnection getConnection() {
		DBConnection conn = null;
		if (freeConnections.size() > 0) {
			// Pick the first Connection from the List
			// to get round-robin usage
			conn = freeConnections.get(0);
			freeConnections.remove(0);
			try {
				if (conn.isClosed()) {
					log.warn("Removed bad connection from " + name);
					// Try again recursively
					conn = getConnection();
				}
			} catch (SQLException e) {
				log.warn("Removed bad connection from " + name, e);
				// Try again recursively
				conn = getConnection();
			}
		} else if (maxConn == 0 || checkedOut < maxConn) {
			conn = newConnection(0);
		} 
		if (conn != null) {
			checkedOut++;
		}
		log.debug("getConnection(): checkedOut = " + checkedOut + " conn: " +conn);
		return conn;
	}
	
	private DBConnection newConnection(int retryCount) {
		Connection con = null;
		try {
			if (user == null) {
				con = DriverManager.getConnection(URL);
			} else {
				con = DriverManager.getConnection(URL, user, password);
			}
			log.debug("Created a new connection in pool " + name + " conn: " +con);
			// test connection
			if (!getDriverName().contains(".ibm.db2.")) {
				if (!con.isValid(10)) {
					if (retryCount > 3) fail("retried 3 times and failed");
					return newConnection(retryCount+1);
				}
			}
			return new DBConnection(this,con);
		} catch (SQLException e) {
			log.error("Can't create a new connection for " + URL, e);
			return null;
		}
	}

	protected synchronized void release() {
		for (DBConnection c : freeConnections) {
			try {
				c.close();
				log.debug("Closed connection for pool " + name + " conn: " +c);
			} catch (SQLException e) {
				log.error("Can't close connection for pool " + name, e);
			}			
		}
		freeConnections.clear();
	}

}
